/*
 * SubRoutePropertiesFrame.java
 *
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.apps.rtemaster.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import org.fpdev.core.transit.SubRoute;
import org.fpdev.apps.rtemaster.RouteMaster;
import java.awt.event.*;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.border.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.fpdev.core.SysProps;
import org.fpdev.core.transit.CalendarService;
import org.fpdev.core.transit.Timetable;
import org.fpdev.util.gui.EditCheckBoxRow;
import org.fpdev.util.gui.EditPropertyRow;
import org.fpdev.util.gui.EditPropertyRowListener;
import org.fpdev.util.gui.EditTextFieldRow;
import org.fpdev.util.gui.GUIFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class SubRoutePropertiesFrame extends JFrame implements ActionListener, EditPropertyRowListener {

  private RouteMaster av_;
  private SubRoute sub_;

  // property editor rows
  private EditPropertyRow idRow_,  nameRow_,  suffixRow_,  bikeRow_;

  // profiles panel
  private JList profileList_;
  private DefaultListModel profileListModel_;
  private JButton applyProfileBtn_, deleteProfileBtn_, createProfileBtn_;

  // frequency chart / editor
  private FrequencyChart chart_;
  private JComboBox dayCB_,  freqCB_,  fromCB_,  toCB_,  mphCB_;
  private JButton setFreqBtn_,  writeTableBtn_;

  private JButton closeBtn_;

  private List<CalendarService> servicesList_;
  private Map<String, ServiceProfile> profiles_;

  public SubRoutePropertiesFrame(RouteMaster av) {
    super("Subroute Properties");

    av_ = av;

    closeBtn_ = GUIFactory.newButton("Close Window", 120, this);

    JPanel btnRow = new JPanel();
    btnRow.setLayout(new BoxLayout(btnRow, BoxLayout.X_AXIS));
    btnRow.add(Box.createHorizontalGlue());
    btnRow.add(closeBtn_);
    btnRow.add(Box.createHorizontalGlue());

    idRow_ = new EditTextFieldRow("ID", "", this);
    nameRow_ = new EditTextFieldRow("Name", "", this);
    suffixRow_ = new EditTextFieldRow("Suffix", "", this);
    bikeRow_ = new EditCheckBoxRow("Bike-Friendly", true, this);

    JPanel propsPanel = new JPanel();
    propsPanel.setLayout(new BoxLayout(propsPanel, BoxLayout.Y_AXIS));
    //propsPanel.add(new JLabel("Properties:"));
    propsPanel.add(idRow_);
    propsPanel.add(Box.createVerticalStrut(5));
    propsPanel.add(nameRow_);
    propsPanel.add(Box.createVerticalStrut(5));
    propsPanel.add(suffixRow_);
    propsPanel.add(Box.createVerticalStrut(5));
    propsPanel.add(bikeRow_);

    JPanel leftPanel = new JPanel(new BorderLayout());
    leftPanel.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 5));
    leftPanel.add(propsPanel, BorderLayout.NORTH);
    leftPanel.add(btnRow, BorderLayout.SOUTH);
    //leftPanel.setMaximumSize(new Dimension(140, 280));

    // profiles panel
    JPanel profiles = new JPanel(new BorderLayout());
    profiles.setBorder(new CompoundBorder(new TitledBorder("Service Profiles"), new EmptyBorder(0,2,3,2)));
    profileListModel_ = new DefaultListModel();
    profileList_ = new JList(profileListModel_);
    JScrollPane profileListSP = new JScrollPane(profileList_);
    applyProfileBtn_ = GUIFactory.newButton("Apply", 60, this);
    deleteProfileBtn_ = GUIFactory.newButton("Delete", 60, this);
    createProfileBtn_ = GUIFactory.newButton("Create from Current", 124, this);

    JPanel profilesBtnArea = GUIFactory.newColumnPanel(), profilesBtnTopRow = GUIFactory.newRowPanel();

    profilesBtnTopRow.add(Box.createHorizontalGlue());
    profilesBtnTopRow.add(applyProfileBtn_);
    profilesBtnTopRow.add(Box.createHorizontalStrut(4));
    profilesBtnTopRow.add(deleteProfileBtn_);
    profilesBtnTopRow.add(Box.createHorizontalGlue());
    profilesBtnTopRow.setAlignmentX(Component.CENTER_ALIGNMENT);
    profilesBtnArea.add(profilesBtnTopRow);
    profilesBtnArea.add(Box.createVerticalStrut(4));
    createProfileBtn_.setAlignmentX(Component.CENTER_ALIGNMENT);
    profilesBtnArea.add(createProfileBtn_);
    profilesBtnArea.setBorder(new EmptyBorder(5,0,0,0));
    profiles.add(profileListSP, BorderLayout.CENTER);
    profiles.add(profilesBtnArea, BorderLayout.SOUTH);
    profiles.setPreferredSize(new Dimension(150,0));

    JPanel topPanel = GUIFactory.newRowPanel();
    topPanel.add(leftPanel);
    topPanel.add(profiles);

    // service chart panel

    servicesList_ = new ArrayList<CalendarService>();
    dayCB_ = GUIFactory.newComboBox(55);
    /*dayCB_.addItem("M-F");
    dayCB_.addItem("Sat");
    dayCB_.addItem("Sun");*/
    for(CalendarService service : av_.getEngine().getRoutes().getCalendarServices().getServices()) {
      dayCB_.addItem(service.getName());
      servicesList_.add(service);
    }
    dayCB_.addActionListener(this);

    writeTableBtn_ = GUIFactory.newButton("Write Table", 100, this);

    JPanel freqTopRow = new JPanel();
    freqTopRow.setLayout(new BoxLayout(freqTopRow, BoxLayout.X_AXIS));
    freqTopRow.add(GUIFactory.newLabel("Day of Week: "));
    freqTopRow.add(dayCB_);
    freqTopRow.add(Box.createHorizontalStrut(5));
    freqTopRow.add(writeTableBtn_);

    chart_ = new FrequencyChart(432, 158);
    JPanel chartPanel = new JPanel();
    chartPanel.setLayout(new BoxLayout(chartPanel, BoxLayout.Y_AXIS));
    chartPanel.add(Box.createVerticalStrut(5));
    chartPanel.add(chart_);
    chartPanel.add(Box.createVerticalStrut(5));
    chartPanel.setPreferredSize(new Dimension(432, 158));

    JPanel setFreqRow = new JPanel();
    setFreqRow.setLayout(new BoxLayout(setFreqRow, BoxLayout.X_AXIS));
    freqCB_ = GUIFactory.newComboBox(40);
    for (int i = 1; i <= 10; i++) {
      freqCB_.addItem(i);
    }
    freqCB_.addItem(12);
    freqCB_.addItem(15);
    freqCB_.addItem(20);
    for (int i = 30; i <= 90; i += 15) {
      freqCB_.addItem(i);
    }
    freqCB_.addItem("X");
    freqCB_.setSelectedIndex(9);

    toCB_ = GUIFactory.newComboBox(45);
    initTimeCB(toCB_);
    fromCB_ = GUIFactory.newComboBox(45);
    initTimeCB(fromCB_);
    mphCB_ = GUIFactory.newComboBox(40);
    for (int i = 5; i <= 50; i += 5) {
      mphCB_.addItem(i);
    }

    setFreqBtn_ = GUIFactory.newButton("Set", 40, this);

    //setFreqRow.add(dayCB_);
    //setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(freqCB_);
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(GUIFactory.newLabel("min freq: "));
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(fromCB_);
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(GUIFactory.newLabel("to"));
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(toCB_);
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(GUIFactory.newLabel("@"));
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(mphCB_);
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(GUIFactory.newLabel("MPH"));
    setFreqRow.add(Box.createHorizontalStrut(5));
    setFreqRow.add(setFreqBtn_);


    JPanel bottomPanel = new JPanel(new BorderLayout());
    //timesPanel.setLayout(new BoxLayout(timesPanel, BoxLayout.Y_AXIS));
    bottomPanel.setBorder(BorderFactory.createTitledBorder("Service Frequency Chart"));
    bottomPanel.add(freqTopRow, BorderLayout.NORTH);
    //timesPanel.add(Box.createVerticalStrut(5));
    bottomPanel.add(chartPanel, BorderLayout.CENTER);
    //timesPanel.add(Box.createVerticalStrut(5));
    bottomPanel.add(setFreqRow, BorderLayout.SOUTH);
    bottomPanel.setSize(450, 280);

    JPanel buttonRow = GUIFactory.newRowPanel();
    buttonRow.add(Box.createHorizontalGlue());
    buttonRow.add(closeBtn_);
    buttonRow.add(Box.createHorizontalGlue());


    JPanel mainPanel = GUIFactory.newColumnPanel();// new JPanel();//new GridLayout(1,2));
    //mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
    mainPanel.add(topPanel);
    mainPanel.add(bottomPanel);
    mainPanel.add(buttonRow);

    mainPanel.setBorder(new EmptyBorder(5, 10, 5, 10));
    getContentPane().add(mainPanel);

    this.pack();
    this.setSize(new Dimension(480, 440));

    initServiceProfiles();

  }

  private void initServiceProfiles() {
    profiles_ = new HashMap<String, ServiceProfile>();

    // read from file
    try {
      String filename = av_.getEngine().getProperty(SysProps.FP_HOME)+"conf"+File.separator+"serviceprofiles.xml";
      if(!new File(filename).exists()) return;
      DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
      Document doc = docBuilder.parse(filename);
      NodeList docNodes = doc.getChildNodes();
      Node thisNode = docNodes.item(0);
      if (docNodes.getLength() != 1 && !thisNode.getNodeName().toLowerCase().equals("serviceprofiles")) {
        System.out.println("Not a valid service profiles datafile");
        return;
      }

      NodeList profileNodes = thisNode.getChildNodes();
      for (int i = 0; i < profileNodes.getLength(); i++) {
        Node profileNode = profileNodes.item(i);
        if(profileNode.getNodeName().toLowerCase().equals("profile")) {
          ServiceProfile profile = new ServiceProfile(profileNode);
          profiles_.put(profile.getName(), profile);
          profileListModel_.addElement(profile.getName());
        }
      }
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }

  private void applyServiceProfile() {
    if(profileList_.getSelectedIndex() == -1) return;
    ServiceProfile profile = profiles_.get(profileList_.getSelectedValue());
    profile.applyToSubRoute(sub_);
    chart_.repaint();
    av_.getRouteOps().updateTimetableInGUI();
    av_.getGUI().getRouteSelectionPanel().updateSubs();
    av_.getRouteOps().getRoutes().changesMade();
  }

  private void createServiceProfile() {
    String name = JOptionPane.showInputDialog("Profile Name");
    if(profiles_.containsKey(name)) {
      JOptionPane.showMessageDialog(this, "A profile by that name already exists");
      return;
    }
    ServiceProfile profile = new ServiceProfile(name, sub_);
    profiles_.put(profile.getName(), profile);
    writeProfiles();
    profileListModel_.addElement(profile.getName());
  }

  private void deleteServiceProfile() {
    if(profileList_.getSelectedIndex() == -1) return;
    profiles_.remove(profileList_.getSelectedValue());
    profileListModel_.removeElement(profileList_.getSelectedValue());
    writeProfiles();
  }

  private void writeProfiles() {
    try {
      String filename = av_.getEngine().getProperty(SysProps.FP_HOME)+"conf"+File.separator+"serviceprofiles.xml";
      FileWriter writer = new FileWriter(filename);

      // write opening tags
      writer.write("<?xml version=\"1.0\"?>\n");
      writer.write("<serviceprofiles>\n");

      for(ServiceProfile profile : profiles_.values()) writer.write(profile.toXml());

      // write closing tags
      writer.write("</serviceprofiles>\n");
      writer.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void initTimeCB(JComboBox cb) {
    for (int i = 4; i <= 11; i++) {
      cb.addItem(i + "a");
    }
    cb.addItem("12p");
    for (int i = 1; i <= 11; i++) {
      cb.addItem(i + "p");
    }
    cb.addItem("12a");
    for (int i = 1; i <= 4; i++) {
      cb.addItem(i + "a");
    }
  }

  private void applyFrequency() {
    int from = fromCB_.getSelectedIndex();
    int to = toCB_.getSelectedIndex();
    if (to <= from) {
      av_.msg("Invalid time range");
      return;
    }
    //Timetable.DayOfWeek day = Timetable.toDayOfWeek(dayCB_.getSelectedIndex() + 1);
    CalendarService service = servicesList_.get(dayCB_.getSelectedIndex());
    String headwayStr = freqCB_.getSelectedItem().toString();
    int headway = (headwayStr.equals("X")) ? 0 : Integer.parseInt(headwayStr);
    sub_.setHeadway(service, from, to, headway, (Integer) mphCB_.getSelectedItem());
    if(sub_.getRunCount(service) == 0) sub_.clearTable(service);
    chart_.repaint();
    av_.getRouteOps().updateTimetableInGUI();
    av_.getGUI().getRouteSelectionPanel().updateSubs();
  }

  private void writeTable() {
    //Timetable.DayOfWeek day = Timetable.toDayOfWeek(dayCB_.getSelectedIndex() + 1);
    CalendarService service = servicesList_.get(dayCB_.getSelectedIndex());
    sub_.writeTableToFile(service, av_.getEngine().getDataPackage().getPath());

    av_.getRouteOps().writeRoutesFile();
    av_.getGUI().getRouteSelectionPanel().updateSubs();
  }

  public void makeVisible(Container container) {
    if (isVisible()) {
      return;
    }
    this.setLocation((container.getWidth() / 2) - getWidth() / 2 + container.getX(),
            (container.getHeight() / 2) - getHeight() / 2 + container.getY());
    setVisible(true);
  }

  public void setSubRoute(SubRoute sub) {
    sub_ = sub;
    this.setTitle(sub.getMasterID()+ " Properties");
    idRow_.setPropertyValue(sub.getID());
    nameRow_.setPropertyValue(sub.getName());
    suffixRow_.setPropertyValue(sub.getRouteSuffix());
    chart_.repaint();
  }


  @Override
  public void actionPerformed(ActionEvent e) {

    if (e.getSource() == applyProfileBtn_) applyServiceProfile();
    if (e.getSource() == deleteProfileBtn_) deleteServiceProfile();
    if (e.getSource() == createProfileBtn_) createServiceProfile();

    if (e.getSource() == dayCB_) {
      chart_.repaint();
    }
    if (e.getSource() == writeTableBtn_) writeTable();
    if (e.getSource() == closeBtn_) {
      setVisible(false);
    }
    if (e.getSource() == setFreqBtn_) applyFrequency();
  }

  @Override
  public void propertyEdited(String name, Object value) {
    if (name.equals("ID")) {
      if (sub_.setID((String) value)) {
        av_.getGUI().getRouteSelectionPanel().updateSubs();
        av_.getGUI().getRouteSelectionPanel().selectSub(sub_);
        av_.clearTransitElements();
      } else {
        av_.msg("Invalid subroute id: " + value);
        idRow_.setPropertyValue(sub_.getID());
      }
    }
    if (name.equals("Name")) {
      sub_.setName((String) value);
    }
    if (name.equals("Suffix")) {
      sub_.setRouteSuffix((String) value);
    }
  }

  public class FrequencyChart extends JPanel {

    private int width_,  height_,  hspace_,  vspace_,  bmargin_,  maxr_;

    private int mxdown_, mxcur_;

    public FrequencyChart(int w, int h) {
      super();
      setSize(w, h);
      width_ = w;
      height_ = h;
      hspace_ = 3;
      vspace_ = 2;
      bmargin_ = 16;
      maxr_ = 12;

      mxdown_ = mxcur_ = 0;
      addMouseListener(new MouseAdapter() {

        @Override
        public void mouseClicked(MouseEvent e) {

        }

        @Override
        public void mousePressed(MouseEvent e) {
          mxdown_ = e.getX();
          //System.out.println("mx down = "+mxdown_);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
          repaint();
          int start = (Math.min(mxdown_, mxcur_)+(width_/48)) / (width_/24);
          int end = start + (Math.abs(mxcur_-mxdown_)+(width_/48)) / (width_/24);
          if(start > 24) start = 24;
          if(end > 24) end = 24;
          fromCB_.setSelectedIndex(start);
          toCB_.setSelectedIndex(end);
          mxdown_ = mxcur_ = 0;
          
        }
      });

      addMouseMotionListener(new MouseMotionAdapter() {

        /*@Override
        public void mouseMoved(MouseEvent e) {

        }*/

        @Override
        public void mouseDragged(MouseEvent e) {
          //System.out.println("dragged mx = "+e.getX());
          mxcur_ = e.getX();
          repaint();
        }
      });
    }

    @Override
    public void paintComponent(Graphics g) {
      //System.out.println("paint chart "+width_+" "+height_);
      g.setColor(Color.WHITE);
      g.fillRect(0, 0, width_, height_);
      g.setColor(new Color(180,180,180));
      g.fillRect(0, height_ - bmargin_, width_, bmargin_);
      int cw = width_/24 - hspace_;
      int ch = (height_ - bmargin_ - vspace_ * maxr_) / maxr_;
      for (int h = 0; h < 24; h++) {
        if(h % 2 == 0) {
          g.setColor(new Color(220,220,220));
          g.fillRect(h*(cw + hspace_), height_ - bmargin_, cw + hspace_, bmargin_);
        }
        int hr = (4 + h) % 12;
        hr = (hr == 0 ? 12 : hr);
        g.setColor(Color.BLACK);
        g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 9));
        g.drawString(""+hr , h * (cw + hspace_) + 2 + (hr <10 ? cw/4 : 0), height_ - 4);

        CalendarService service = servicesList_.get(dayCB_.getSelectedIndex());
        int runs = sub_.runsPerHour(service, h);

        for (int r = 0; r < runs; r++) {
          g.setColor(Color.BLUE);
          g.fillRect(h * (cw + hspace_) +hspace_/2, (maxr_ - r) * (ch + vspace_), cw, ch);
        }
      }

      // draw mouse-drag selection outline, if applicable
      if(mxcur_ - mxdown_ != 0) {
        g.setColor(Color.RED);
        g.drawRect(Math.min(mxdown_, mxcur_), height_ - bmargin_, Math.abs(mxdown_-mxcur_), bmargin_-1);
      }
    }
  }

  public class ServiceProfile {

    private String name_;
    private Map<String, HourInfo[]> data_; //key = service id

    private SubRoute sub_;

    public ServiceProfile(String name, SubRoute sub) {
      name_ = name;
      sub_ = sub;

      data_ = new HashMap<String, HourInfo[]>();
      for(CalendarService service : av_.getEngine().getRoutes().getCalendarServices().getServices())
        initDataForDayOfWeek(service);

      /*initDataForDayOfWeek(Timetable.DayOfWeek.WEEKDAY);
      initDataForDayOfWeek(Timetable.DayOfWeek.SATURDAY);
      initDataForDayOfWeek(Timetable.DayOfWeek.SUNDAY);*/
    }

    public ServiceProfile(Node node) {
      name_ = node.getAttributes().getNamedItem("name").getNodeValue();
      data_ = new HashMap<String, HourInfo[]>();

      NodeList svcNodes = node.getChildNodes();
      for (int id = 0; id < svcNodes.getLength(); id++) {
        Node svcNode = svcNodes.item(id);
        if(svcNode.getNodeName().equals("service")) {
          //Timetable.DayOfWeek dow = Timetable.toDayOfWeek(day.getAttributes().getNamedItem("name").getNodeValue());
          String serviceID = svcNode.getAttributes().getNamedItem("id").getNodeValue();
          HourInfo[] hourData = new HourInfo[24];
          NodeList hourNodes = svcNode.getChildNodes();
          for (int ih = 0; ih < hourNodes.getLength(); ih++) {
            Node hour = hourNodes.item(ih);
            if(hour.getNodeName().equals("hour")) {
              int h = Integer.parseInt(hour.getAttributes().getNamedItem("h").getNodeValue());
              int headway = Integer.parseInt(hour.getAttributes().getNamedItem("headway").getNodeValue());
              int offset = Integer.parseInt(hour.getAttributes().getNamedItem("offset").getNodeValue());
              int mph = Integer.parseInt(hour.getAttributes().getNamedItem("mph").getNodeValue());
              hourData[h] = new HourInfo(headway, offset, mph);
            }
          }
          data_.put(serviceID, hourData);
        }
      }
    }

    public String getName() {
      return name_;
    }

    public String toXml() {
      String xml = "";
      xml += "<profile name=\""+name_+"\">\n";
      for(Map.Entry<String, HourInfo[]> entry : data_.entrySet()) {
        xml += "  <service id=\""+entry.getKey()+"\">\n";
        HourInfo[] hours = entry.getValue();
        for(int h=0; h<24; h++)
          xml += "    <hour h=\""+h+"\" headway=\""+hours[h].headway_+"\" offset=\""+hours[h].offset_+"\" mph=\""+hours[h].mph_+"\" />\n";
        xml += "  </service>\n";
      }
      xml += "</profile>\n";

      return xml;
    }

    public int totalDailyRunCount(CalendarService service) {
      int count = 0;
      HourInfo[] hours = data_.get(service.getID());
      for(int h=0; h<24; h++) count += hours[h].runCount();
      return count;
    }

    public void applyToSubRoute(SubRoute sub) {
      for(Map.Entry<String, HourInfo[]> entry : data_.entrySet()) {
        String serviceID = entry.getKey();
        CalendarService service = av_.getEngine().getRoutes().getCalendarServices().getService(serviceID);
        HourInfo[] hours = entry.getValue();
        if(totalDailyRunCount(service) > 0) {
          for(int h=0; h<24; h++) {
            //System.out.println("applying h="+h+" headway="+hours[h].headway_);
            
            sub.setHeadway(service, h, h+1, hours[h].headway_, hours[h].offset_, hours[h].mph_);
          }
        }
        else { // no service for this day
          if(sub.hasTable(service)) sub.clearTable(service);
        }
      }
    }

    private void initDataForDayOfWeek(CalendarService service) {

      HourInfo[] hourData = newHourInfoArray();
      data_.put(service.getID(), hourData);
      if(sub_.hasTable(service)) {
        for(int h=0; h<24; h++) {
          hourData[h].headway_ = (int) sub_.getTable(service).averageHeadway(h);
          hourData[h].offset_ = sub_.getTable(service).offset(h);
          hourData[h].mph_ = (int) sub_.getTable(service).averageSpeed(h, sub_);
        }
      }
    }

    private HourInfo[] newHourInfoArray() {
      HourInfo[] arr = new HourInfo[24];
      for (int i = 0; i < 24; i++) {
        arr[i] = new HourInfo();
      }
      return arr;
    }

    private class HourInfo {
      int headway_, offset_, mph_;

      public HourInfo(int headway, int offset, int mph) {
        headway_ = headway;
        offset_ = offset;
        mph_ = mph;
      }

      public HourInfo() {
        this(0, 0, 0);
      }

      public int runCount() {
        return (headway_ == 0) ? 0 : 60 / headway_ + 1;
      }
    }
  }
}
